/**
 * Copyright (c) 2009 Teoti Graphix, LLC See:
 *     http://tkarchitecture.teotigraphix.org
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 * 
 * @author Michael Schmalle mschmalle@teotigraphix.com
 */
package org.teotigraphix.dx.patterns.layer
{

import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;

import mx.core.IInvalidating;
import mx.core.IMXMLObject;

import org.teotigraphix.dx.core.ToolKitUtil;
import org.teotigraphix.dx.event.ComponentLayerEvent;
import org.teotigraphix.dx.interfaces.IActivate;
import org.teotigraphix.dx.interfaces.IComponentAware;
import org.teotigraphix.dx.interfaces.IComponentLayer;
import org.teotigraphix.dx.interfaces.IComponentLayerClient;

// TODO (mschmalle) show how to implement these layers in MXML

/**
 * The abstract base class for a layer in a <code>IToolKitComponent</code>.
 * 
 * <p>There are no public methods in this class, subclasses implement
 * the functionality found in this class and override as neccessary.
 * Most layers found in a toolkit element define their interfaces such
 * the are recognizable from eachother.</p>
 * 
 * <p>Also note that each subclass is responsible for dispatch add and remove
 * events.</p>
 * 
 * @copyright Teoti Graphix, LLC
 * @author Michael Schmalle
 * @date 07-07-09
 * 
 * @see org.teotigraphix.dx.interfaces.IModelLayer
 * @see org.teotigraphix.dx.interfaces.ISkinLayer
 * @see org.teotigraphix.dx.interfaces.IBehaviorLayer
 */
public class ComponentLayer
	   extends EventDispatcher
	   implements IMXMLObject, IComponentLayer, 
				  IComponentAware, IActivate
{
	include "../../core/Version.as"
	
	//--------------------------------------------------------------------------
	//
	//  Private :: Variables
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @private
	 */
	private var _collection:Array;
	
	//--------------------------------------------------------------------------
	//
	//  IActivate API :: Properties
	//
	//--------------------------------------------------------------------------
	
	//----------------------------------
	//  isActive
	//----------------------------------
	
	/**
	 * @private
	 */
	private var _isActive:Boolean = false;

	/**
	 * @copy org.teotigraphix.dx.interfaces.IComponentLayer#isActive
	 * 
	 * @uses #commitComponent()
	 */
	public function get isActive():Boolean
	{
		return _isActive;
	}
	
	//--------------------------------------------------------------------------
	//
	//  IComponentAware API :: Properties
	//
	//--------------------------------------------------------------------------
	
	//----------------------------------
	//  component
	//----------------------------------
	
	/**
	 * @private
	 */
	private var _component:IEventDispatcher;

	/**
	 * @copy org.teotigraphix.dx.interfaces.IComponentAware#component
	 * 
	 * @uses #commitComponent()
	 */
	public function get component():IEventDispatcher
	{
		return _component;
	}
	
	/**
	 * @private
	 */	
	public function set component(value:IEventDispatcher):void
	{
		_component = value;
		commitComponent();
	}
	
	//----------------------------------
	//  className
	//----------------------------------
	
	/**
	 * @private
	 */
	private var _className:String;
	
	/**
	 * @copy org.teotigraphix.dx.interfaces.IToolKitComponentAware#className
	 */
	public function get className():String
	{
		return _className;
	}
	
	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------
	
	/**
	 * Constructor.
	 */
	public function ComponentLayer()
	{
		_className = ToolKitUtil.getClassName(this);
	}
	
	//--------------------------------------------------------------------------
	//
	//  IMXMLObject API :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @private
	 */
	public function initialized(document:Object, id:String):void
	{
	}
	
	//--------------------------------------------------------------------------
	//
	//  IActivate API :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @copy org.teotigraphix.dx.interfaces.IActivate#activate()
	 * 
	 * @event org.teotigraphix.dx.event.ToolKitLayerEvent#LAYER_ACTIVATE
	 */
	public function activate(host:IEventDispatcher):void
	{
		_isActive = true;
		
		var e:ComponentLayerEvent = new ComponentLayerEvent(
			ComponentLayerEvent.LAYER_ACTIVATE, this,
			getAllProxies(), host);
		
		component = host;
		
		if (_collection)
		{
			var len:int = _collection.length;
			for (var i:int = 0; i < len; i++)
			{
				var info:ProxyInfo = _collection[i];
				dispatchAddEvent(info.api, info.proxy);
				info.proxy.activate(host);
			}
		}
		
		dispatchEvent(e);
		
		if (component)
			component.dispatchEvent(e);
	}
	
	/**
	 * @copy org.teotigraphix.dx.interfaces.IActivate#activate()
	 * 
	 * @event org.teotigraphix.dx.event.ToolKitLayerEvent#LAYER_DEACTIVATE
	 */
	public function deactivate(host:IEventDispatcher):void
	{
		_isActive = false;
		
		var e:ComponentLayerEvent = new ComponentLayerEvent(
			ComponentLayerEvent.LAYER_DEACTIVATE, this,
			getAllProxies(), host);
		
		if (_collection)
		{
			var len:int = _collection.length;
			for (var i:int = 0; i < len; i++)
			{
				var info:ProxyInfo = _collection[i];
				dispatchAddEvent(info.api, info.proxy);
				info.proxy.deactivate(host);
			}
		}
		
		// TODO (mschmalle) test this and see if we need to unassign component
		// after the dispatch
		component = null;
		
		dispatchEvent(e);
		
		if (host)
			host.dispatchEvent(e);
	}
	
	//--------------------------------------------------------------------------
	//
	//  Protected API :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * Registers a <code>proxy</code> to the <code>proxies</code> map.
	 * 
	 * @param api A Class api used as the key for the proxy instance.
	 * @param service A <code>IComponentLayerClient</code> to register to the 
	 * <code>proxies</code> map.
	 * 
	 * @throws Error The proxy api cannot be null
	 * @throws Error The proxy's interface does not match the api
	 * @throws Error api exists, use removeService() first
	 */
	protected function registerProxy(api:Class, proxy:IComponentLayerClient):void
	{
		// TODO (mschmalle) make ProxyLayerError with enum ID's
		
		if (!api)
			throw Error("The proxy api cannot be null");		
		
		// do interface check isInstance()
		if (proxy && !(proxy is api))
			throw Error("The proxy's interface does not match the api");
		
		if (!_collection)
			_collection = [];
		
		// this is going to be the RULE, if a service already exists
		// under the api, throw Error, use removeService() before registering
		if (contains(api))
			throw new Error(api + " exists, use removeProxy() first");
		
		if (proxy is IComponentAware)
			IComponentAware(proxy).component = _component;
		
		addItem(api, proxy);
		
		if (_component)
		{
			var iinv:IInvalidating = _component as IInvalidating;
			
			iinv.invalidateProperties();
			iinv.invalidateSize();
			iinv.invalidateDisplayList();
			
			// added see if this causes problems, this is the logical place
			// if we ahve no element and this dosn't get activated, the
			// proxy will be activated when this layer is activated with
			// the element
			proxy.activate(_component);			
		}
		
		dispatchAddEvent(api, proxy);
	}
	
	/**
	 * Removes a <code>IComponentLayerClient</code> from the 
	 * <code>proxies</code> map.
	 * 
	 * @param api A Class api used as the key for the proxy instance.
	 */
	protected function removeProxy(api:Class):IComponentLayerClient
	{
		if (!_collection)
			return null;
		
		// no need to throw error since it is returning null
		if (!contains(api))
			return null;
		
		var info:ProxyInfo = removeItem(api);
		var old:IComponentLayerClient = info.proxy;
		
		if (_collection.length == 0)
			_collection = null;			
		
		old.deactivate(_component);
		
		dispatchRemoveEvent(api, old);
		
		// NOTE :: we need to cal this after the dispatch since the behavior might
		// be listening to 'componentHandlers', if the element is removed before, 
		// those handlers get removed to soon
		if (old is IComponentAware)
			IComponentAware(old).component = null;		
		
		return old;
	}
	
	/**
	 * Tests for a <code>IComponentLayerClient</code> registered in the 
	 * <code>proxies</code> map using a Class api key.
	 * 
	 * @param api A Class api used as the key for the proxy instance.
	 */
	protected function hasProxy(api:Class):Boolean
	{
		return contains(api);
	}
	
	/**
	 * Returns a <code>IComponentLayerClient</code> registered with the 
	 * <code>proxies</code> map using the registered proxies Class api key.
	 * 
	 * @param api A Class api used as the key for the proxy instance.
	 */
	protected function getProxy(api:Class):IComponentLayerClient
	{
		if (!contains(api))
			return null;
				
		return getItem(api).proxy;
	}
	
	/**
	 * Returns an array of all proxies registered to this layer.
	 */
	protected function getAllProxies():Array
	{
		var result:Array = [];
		if (!_collection)
			return result;
		
		var len:int = _collection.length;
		for (var i:int = 0; i < len; i++)
		{
			var info:ProxyInfo = _collection[i];
			result.push(info.proxy);
		}
		return result;
	}
	
	//--------------------------------------------------------------------------
	//
	//  Protected Commit :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * Commits the <code>component</code> property.
	 */
	protected function commitComponent():void
	{
	}
	
	//--------------------------------------------------------------------------
	//
	//  Protected Event :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * Creates and returns a layer event.
	 * 
	 * @param type A string indicating the type of layer event.
	 * @param load A boolean indicating whether to return a fully loaded
	 * layer event.
	 */
	protected function createLayerEvent(type:String, load:Boolean = false):Event
	{
		return null;
	}
	
	/**
	 * Dispatches the custom add event.
	 * 
	 * <p>Override in subclasses.</p>
	 * 
	 * @param api The Class api.
	 * @param proxy The in operation proxy instance.
	 */
	protected function dispatchAddEvent(api:Class, proxy:IComponentLayerClient):void
	{
	}
	
	/**
	 * Dispatches the custom remove event.
	 * 
	 * <p>Override in subclasses.</p>
	 * 
	 * @param api The Class api.
	 * @param proxy The in operation proxy instance.
	 */
	protected function dispatchRemoveEvent(api:Class, proxy:IComponentLayerClient):void
	{
	}
	
	//--------------------------------------------------------------------------
	//
	//  Private :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @private
	 */
	private function contains(api:Class):Boolean
	{
		if (!_collection)
			return false;
		
		var len:int = _collection.length;
		for (var i:int = 0; i < len; i++)
		{
			if (_collection[i].api === api)
				return true;
		}
		return false;
	}
	
	/**
	 * @private
	 */
	private function addItem(api:Class, proxy:IComponentLayerClient):ProxyInfo
	{
		var item:ProxyInfo = new ProxyInfo(api, proxy);
		_collection.push(item);
		return item;
	}	
	
	/**
	 * @private
	 */
	private function getItem(api:Class):ProxyInfo
	{
		var len:int = _collection.length;
		for (var i:int = 0; i < len; i++)
		{
			if (_collection[i].api === api)
				return _collection[i];
		}
		return null;
	}	
	
	/**
	 * @private
	 */
	private function removeItem(api:Class):ProxyInfo
	{
		var len:int = _collection.length;
		for (var i:int = 0; i < len; i++)
		{
			if (_collection[i].api === api)
			{
				var info:ProxyInfo = _collection[i];
				_collection.splice(i, 1);
				return info;
			}
		}
		return null;
	}
	
	/**
	 * @private
	 */
	private function getAllItems():Array
	{
		var result:Array = [];
		var len:int = _collection.length;
		for (var i:int = 0; i < len; i++)
		{
			result.push(_collection[i]);
		}
		return result;
	}
}
}

import org.teotigraphix.dx.interfaces.IComponentLayerClient;

/**
 * @private
 * 
 * Internal housekeeping.
 * 
 * @copyright Teoti Graphix, LLC
 * @author Michael Schmalle
 */
class ProxyInfo
{
	//--------------------------------------------------------------------------
	//
	//  Public :: Variables
	//
	//--------------------------------------------------------------------------
	
	//----------------------------------
	//  api
	//----------------------------------
	
	/**
	 * The <code>Class</code> api key for the proxy.
	 */
	public var api:Class;
	
	//----------------------------------
	//  proxy
	//----------------------------------
	
	/**
	 * The layer proxy.
	 */	
	public var proxy:IComponentLayerClient;
	
	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------
	
	/**
	 * Constructor.
	 * 
	 * @param api The <code>Class</code> api key for the proxy.
	 * @param proxy The layer proxy.
	 */
	public function ProxyInfo(api:Class, proxy:IComponentLayerClient)
	{
		this.api = api;
		this.proxy = proxy;
	}
}